/**
 * 使用 let name = ref('ls')
 * ref：ref可以针对普通值实现数据响应式；
 * 当设置的值是普通类型，可以将普通类型转成对象，对象中有value属性，指向原来的值；
 * 当在effect中执行的时候，也会和reactive定义的一样，先收集依赖，在执行；
 * 为什么reactive不支持设置普通类型，只能设置对象类型，是因为，Proxy只支持对像；
 * reactive处理不了普通类型的数据
 */

/**
 *（1）ref与reactive的区别：ref内部实现是通过Object.defineProperty；reactive内部实现通过new Proxy
 *
 */
import { reactive } from './../reactive/reactive';
import { track, trigger } from './../reactive/effect';
import { TrackOpTypes, TriggerOpTypes } from "@vue/reactivity";
import { hasChange, isArray, isObject } from './../common/utils';
export function ref(value) {
  return createrRef(value);
}
// 判断如果ref定义的值是对像，则通过reactive添加深层次设置
const convert = (value) => isObject(value) ? reactive(value) : value;
// 带有get和set的类 通过babel转成es5之后，内部执行的是就是Object.defineProperty，所以ref的核心就是Object.defineProperty
class RefImpl {
  public _value; // 声明vule
  public __v_isRef = true; //产生的实例上会被标记上__v_isRef 表示是一个ref属性
  constructor(public rawValue, public shallow) { //在constructor的参数前 标记修饰符，可以根据修饰符的类型，直接放到实例上
    /**
     * public rawValue,public shallow 等同于
     * this._rawValue = rawValue；
     * this.shallow=shallow
     */
    this._value = shallow ? rawValue : convert(rawValue);
  }
  // 类的属性器
  get value() {
    // 收集依赖
    track(this, TrackOpTypes.GET, 'value');
    return this._value
  }
  set value(newValue) {
    if (hasChange(this._value, newValue)) { //通过hasChange判断老值和新值是否相等
      this.rawValue = newValue; //新值替换成老值
      this._value = this.shallow ? newValue : convert(newValue);;
      trigger(this, TriggerOpTypes.SET, 'value', newValue) // 获取到对应的依赖 进行执行
    }
  }
}
function createrRef(rawValue, shallow = false) {
  return new RefImpl(rawValue, shallow)
}

class ObjectRefImpl {
  public __v_isRef = true;
  constructor(public target, public key) { }
  get value() {
    return this.target[this.key];
  }
  set value(newValue) {
    this.target[this.key] = newValue;
  }
}
// 将某一个key对应的值 转化成ref
export function toRef(target, key) { //可以把对象中的值 转化成了ref类型
  return new ObjectRefImpl(target, key)
}
/**
 * 不能直接用解构的方式解构reactive定义的数据，否则数据的响应式会失效。
 * 但是可以使用toRefs包裹起来再解构，那是因为toRefs里循环遍历里data里，
 * 并且每个属性都转成里toRef类型
 * */
export function toRefs(object) { //object可能是数组 也可能是对象
  let ret = isArray(object) ? new Array(object.length) : {};
  for (let key in object) {
    ret[key] = toRef(object, key);
  }
  return ret;
}

